java中单例的几种写法

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。

单例通常有很多种写法,但是性能和效果却是差距挺大。下面列举了几种常见的写法。

一、单例模式常见的写法

  1. 懒汉式(线程不安全)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        
    public class Singleton {
    private static Singleton instance = null;

    private Singlton() {
    }

    public static Singleton getInstance() {
    if(instance == null)
    return instance;
    return instance;
    }
    }

这种写法,在多线程的时候,由于并发访问instance,会导致创建多个instance,从而使得单例模式失效。

  1. 懒汉式(线程安全1)
1
2
3
4
5
6
7
8
9
10
11
12
13
    
public class Singleton {
private static Singleton instance = null;

private Singlton() {
}

public synchronized static Singleton getInstance() {
if(instance == null)
return instance;
return instance;
}
}

这种写法的优点是保证了线程安全,缺点是效率低下,因为instance一旦创建,大部分时间都是多线程在访问instance,因此把同步加在方法上会导致多个线程等待。

  1. 懒汉式(线程安全2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    
public class Singleton {
private static Singleton instance = null;

private Singlton() {
}

public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
instance = new Singleton();
return instance;
}
}
return instance;
}
}

这种写法有效的避免了第2种写法的缺点,当instance被创建成功后,大部分时间多线程访问instance的时候都无需同步;而只有当需要创建instance的时候才需要同步创建instance的代码块。看似这种写法比较完美,但是这种写法有有一个致命的缺点就是,当线程A判断instance等于null的时候,这时线程A被挂起,线程B判断instance为null,同时获取到锁进入了同步代码块,然后成功的创建了instance,最后释放了锁退出了同步代码块。恰好此时线程A成功的获取到锁进入同步代码块继续执行,它也会创建一个instance,这样instance就被创建不止一次,系统中就存在多个instance。

  1. 双重检验式(Double Check Lock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    
public class Singleton {
private static Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
return instance;
}
}
}
return instance;
}
}

另一种类似的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {
private volatile Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
return instance;
}
}
}
return instance;
}
}

双重校验可以避免第3种方法的缺点,当线程获得锁后进入同步代码块后,再进一步确认instance是否为null。通常双重校验锁这种形式会比较好的达到正常的单例的效果。

  1. 饿汉式
1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static Singleton instance = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
return instance;
}
}

这种方式利用ClassLoader的机制保证了单例类只在被类加载器第一次加载的时候,创建一个instance,避免了多线程的同步问题。优点是没有同步代码块,效率高;缺点是Singleton可能会因为多种原因被加载,因此没有实现懒加载。

  1. 饿汉式(静态内部类)
1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

private Singleton() {
}

public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

这种方式相对于第5种方式,虽然由于某种原因导致Singleton类被类加载器加载,但是由于SingletonHolder没有被显示的加载,因此instance还是没有被创建。因此这种形式在某种程度上来说,相对于第5种写法来说延迟了instance的创建。

当单例类由不同的类加载器加载或者能被序列化和反序列化或者可以通过反射来调用私有构造函数的时候,上述的几种方式都不能很好的实现单例的效果,这个需要再进一步讨论。

二、枚举类型实现单例模式

JDK1.5以后,可以通过枚举类型来实现线程安全的、防止序列化和反序列化的、代码简洁的单例模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public enum Singleton {
INSTANCE;
//属性
private int a,b,c;
//构造函数只能为私有的
private Singleton() {

}

public void method1() {

}

public void method2() {

}
}

采用这种方式实现的单例模式是十分推荐的,天然线程安全,天然解决了序列化和反序列化之后、反射调用私有构造函数出现多个对象的问题。同时这也是Effective Java推荐的写法。

三 、References

https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html